package OrderedCoverPackage;

import OLDSTUFF.ClassAlgebra.ComplexExpression;
import OLDSTUFF.ClassAlgebra.Expression;
import OLDSTUFF.ClassAlgebra.ListExpression;
import Estimator.OLDSTUFF_EstimatorExp;
import OLDSTUFF.HierarchyPackage.Hierarchy;
import OLDSTUFF.HierarchyPackage.HierarchyException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * This package defines the methods that all ordered covers, i.e. both base-covers and delta-covers must have in common.
 * Furthermore it implements some the common-funtionality of these covers.
 * Especially the xDenom and condLogLik methods are important and quite complex.
 *
 * @author Martin Haulrich
 */
public abstract class AbstractOC {

    /**
     * Hierarchy that this orderes cover is based on.
     * I.e. the classifications used are from this hierarchy.
     */
    Hierarchy hierarchy;

    /**
     * The prior-estimator used for this cover.
     */
    OLDSTUFF_EstimatorExp prior;

    /**
     * An ordered list of the partitions in the cover.
     * For all partition it is the case that theier superpartitions are to the rigth of them in the list.
     * Left-sisters are to the left and right sisters to the right,
     */
    protected List<OCPartition> partitionList;

    protected OCPartition top;

    public AbstractOC(Hierarchy hierarchy, OLDSTUFF_EstimatorExp prior) {
        this.hierarchy = hierarchy;
        this.prior = prior;

    }

    public PartitionExpressionTuple getPartitionExpression(OCPartition p) {

        PartitionExpressionTuple tuple = new PartitionExpressionTuple();

        List<OCPartition> treeSubsPar = new ArrayList<OCPartition>();

        if (!p.equals(getTop())) {
            getSubtractsRec(p, getParent(p), treeSubsPar);
        }
        
        List<Expression> treeSubsExpr = new ArrayList<Expression>(treeSubsPar.size());
        for (OCPartition sub : treeSubsPar) {
            treeSubsExpr.add(sub.mainExp);
        }

        List<OCPartition> children = getNoGhostChildren(p);

        List<Expression> subsExpr = new ArrayList<Expression>(treeSubsExpr);
        for (OCPartition sub : children) {
            subsExpr.add(sub.mainExp);
        }

        Expression treeSubList = new ListExpression(ListExpression.UNION, treeSubsExpr);
        Expression subList = new ListExpression(ListExpression.UNION, subsExpr);

        tuple.partitionExpression = new ComplexExpression(ComplexExpression.DIFFERENCE, p.mainExp, subList);
        tuple.partitionTreeExpression = new ComplexExpression(ComplexExpression.DIFFERENCE, p.mainExp, treeSubList);


        return tuple;
    }




    /**
     * When finding prior-probablities we need a list of expression for all the partitions before the current partition
     * that should be subtracted from the current. This method returns this list.
     * These partitions are all partitions that are left-siblings of transitive parents to the partition.
     * @param p Partition to find subtracs-list for.
     * @return List of expression for the main-classes of the partitions before the argument-partition in this cover.
     */
    protected Collection<Expression> subtractsExpression(OCPartition p) {

        //List<OCPartition> partitions = new ArrayList<OCPartition>(getChildren(p));
        List<OCPartition> partitions = new ArrayList<OCPartition>(getNoGhostChildren(p));

        if (!p.equals(getTop())) {
            getSubtractsRec(p, getParent(p), partitions);
        }
        List<Expression> res = new ArrayList<Expression>(partitions.size());

        for (OCPartition o : partitions) {
            // Do not include ghost - because ghost does not exist :-)
            if (!o.ghost) {
                res.add(o.mainExp);
            }
        }

        //System.err.println(p.mainExp + " : " + res);
        return res;
    }

    public List<OCPartition> getNoGhostChildren(OCPartition p) {
        List<OCPartition> res = new ArrayList<OCPartition>();
        for (OCPartition ch : getChildren(p)) {
            if (ch.ghost) {
                res.addAll(getNoGhostChildren(ch));
            } else {
                res.add(ch);
            }
        }
        return res;

    }

    /**
     * Recursive method used by subtractsExpression
     * @param current The partition currently "active", i.e. this one whos left children should be added.
     * @param currentTransParent The parent of current.
     * @param res The list of partition that are left-siblings of partition and its transitive parents.
     */
    private void getSubtractsRec(OCPartition current, OCPartition currentTransParent, List<OCPartition> res) {

        List<OCPartition> currentChildren = getChildren(currentTransParent);
        int index = currentChildren.indexOf(current);
//        res.addAll(currentChildren.subList(0, index));
        for (OCPartition ch : currentChildren.subList(0, index)) {
            if (ch.ghost) {
                res.addAll(getNoGhostChildren(ch));
            } else {
                res.add(ch);
            }
        }
        OCPartition newPar = getParent(currentTransParent);
        if (newPar != null) {
            getSubtractsRec(currentTransParent, newPar, res);
        }

    }

    /**
     * Returns the number of partitions in the cover
     * @return Number of partitions in cover.
     */
    public int OCsize() {
        /**
         * @TODO This should of course be made more efficient - simply keep track of how many ghosts there are.
         */
        int ocsize = 0;
        for (OCPartition p : partitionList) {
            if (!p.ghost) {
                ocsize++;
            }
        }

        return ocsize;
    }

    /**
     * Returns the sub-partitions of a goven partition.
     * @param partition Partition to get sub-partitions of.
     * @return Sub-partitions of partition.
     */
    protected List<OCPartition> getChildren(OCPartition partition) {
        if (partition.children != null) {
            return partition.children;
        } else {
            return Collections.<OCPartition>emptyList();
        }
    }

    public Hierarchy getHierarchy() {
        return hierarchy;
    }

    /**
     * Returns the logarimic weigth for a given partition
     * This is given as the empirical probability divided by prior probability.
     * In logaritmics it is subtracted insted.
     * @param partition Partition to get weigth for
     * @return Log of weigth for partition.
     * @throws HierarchyPackage.HierarchyException
     */
    protected double getLogWeight(OCPartition p) throws HierarchyException {
        if (p.ghost) {
            System.err.println("AbstractOC.getLogWeight - ghost - should not happen!!!!!!!!!!!!");
        }
//        return partition.restLogEmpProb - partition.restLogPriorProbDenom;
        return p.resLogPseuEmpProb - p.restLogPriorProbDenom;
    }

    /**
     * Returns the super-partition of a given partition.
     * @param partition Partition to find super-partition of.
     * @return Super-partition of partition.
     */
    protected OCPartition getParent(OCPartition partition) {
        return partition.parent;
    }

    /**
     * Returns the TOP-partition
     * @return TOP-partition int the cover.
     */
    public OCPartition getTop() {
        return top;
    }

    /**
     * Returns an ordered list of the partitions in the cover.
     * For all partition it is the case that theier superpartitions are to the rigth of them in the list.
     * Left-sisters are to the left and right sisters to the right,
     * @return Ordered list of partitions in the cover.
     */
    protected List<OCPartition> partitionList() {
        return partitionList;
    }

    public void toDot(String filename, boolean prune) {
        try {
            Set<OCPartition> pp = new HashSet<OCPartition>(partitionList);
            Set<OCPartition> tSet = new HashSet<OCPartition>();
            for (Iterator<OCPartition> it = pp.iterator(); it.hasNext();) {
                OCPartition p = it.next();
                if ((!p.equals(top)) && p.parent.equals(top)) {
                    if (((p.children == null) || p.children.isEmpty()) && prune) {
                        it.remove();
                        tSet.add(p);
                    }
                }
            }
            PrintWriter out = new PrintWriter(new FileWriter(filename));
            out.println("strict digraph g\n{");
            for (Iterator<OCPartition> it = pp.iterator(); it.hasNext();) {
                OCPartitionTrain p = (OCPartitionTrain) it.next();
                out.print(p.ID + " [label = \"" + p.mainClass.getName() + " " + p.restDataPoints.size());
                out.printf(" %6.3g", p.restLogEmpProb - p.restLogPriorProbDenom);
                out.println("\"]");
            }
            for (Iterator<OCPartition> it = pp.iterator(); it.hasNext();) {
                OCPartitionTrain p = (OCPartitionTrain) it.next();
                String pname = p.mainClass.getName();
                if (p.children != null) {
                    for (OCPartition ch : p.children) {
                        if (!tSet.contains(ch)) {
                            out.println(p.ID + " -> " + ch.ID);
                        }
                    }
                }
            }
            out.println("}");
            out.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (HierarchyException ex) {
            ex.printStackTrace();
        }
    }

    private void toDot2Rec(PrintWriter out, boolean prune, OCPartition p) throws HierarchyException {

        String name = p.mainClass.getName();
//        System.err.print(name + "\t");
//        if (p.ghost) {
//            System.err.println("ghost");
//        } else {
//            System.err.println(getLogWeight(p));
//        }
        name = name.replace("\"", "\\\"");

        if (p.ghost) {
            name = "(" + name + ")";
        }
        out.print(p.ID + " [label = \"" + name + " " + ((OCPartitionTrain) p).restDataPoints.size());

        double w = Double.NaN;
        if (!p.ghost) {
            w = getLogWeight(p);
        }
        out.printf(" (%6.3g", (w));
        out.printf(") %6.3g", p.getTotalWeight());
        out.println("\"]");
        if (p.children != null) {
            for (OCPartition ch : p.children) {
                out.println(p.ID + " -> " + ch.ID);
                toDot2Rec(out, prune, ch);
            }
        }
    }

    public void toDot2(String filename, boolean prune) {
        try {
            PrintWriter out = new PrintWriter(new FileWriter(filename));
            out.println("strict digraph g\n{");
            toDot2Rec(out, prune, top);
            out.println("}");
            out.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (HierarchyException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public String toString() {
        StringBuffer strBuf = new StringBuffer();
        try {
            //    System.err.println(partitionList);
            for (Iterator<OCPartition> it = partitionList.iterator(); it.hasNext();) {
                //          System.err.println("t");
                OCPartition part = (OCPartition) it.next();
                strBuf.append(part.mainClass.getName() + "  (" + part.ID + ")" + System.getProperty("line.separator"));
                //strBuf.append("   " + part.dataPoints + System.getProperty("line.separator"));
                if (part.children != null) {
                    for (Iterator<OCPartition> itCh = part.children.iterator(); itCh.hasNext();) {
                        OCPartition chPart = itCh.next();
                        strBuf.append("  -> " + chPart.mainClass.getName() + System.getProperty("line.separator"));
                    }

                }
            }
        } catch (HierarchyException ex) {
            ex.printStackTrace();
        }

        return strBuf.toString();
    }
}
